// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html
import RecycleListItem from "./RecycleListItem";
import RecycleListLayout, { HorizontalLayout, VerticalLayout } from "./RecycleListLayout";

const { ccclass, property } = cc._decorator;
@ccclass
export default class RectLayout extends RecycleListLayout {
    @property
    spaceX: number = 0;

    @property
    spaceY: number = 0;

    @property
    row: number = 0;

    @property
    col: number = 0;

    /**
     * content能容纳item的行列数
     */
    private _contentRow: number = 0;
    private _contentCol: number = 0;

    /**
     * view能容纳item的行列数
     */
    private _viewRow: number = 0;
    private _viewCol: number = 0;

    private _rowFirstIndex: number = -1;
    private _rowLastIndex: number = -1;

    private _colFirstIndex: number = -1;
    private _colLastIndex: number = -1;

    private _contentPositionChanged: boolean = false;
    private _enableFrameLoad: boolean = false;

    public getItemLength(): number {
        return this.row * this.col;
    }

    public isDataIndexInView(dataIndex: number): boolean {
        let dataRowColIndex = this.dataIndex2RowColIndex(dataIndex);
        return dataRowColIndex.x >= this._rowFirstIndex && dataRowColIndex.x <= this._rowLastIndex
            && dataRowColIndex.y >= this._colFirstIndex && dataRowColIndex.y <= this._colLastIndex;
    }

    public updateLayout() {
        if (this._contentPositionChanged) {
            let itemComp: RecycleListItem;
            for (let i = 0, len = this.row * this.col; i < len; i++) {
                itemComp = this.recycleList.loadItem(i);
                if (!this.isDataIndexInView(itemComp.dataIndex)) {
                    this.recycleList.itemDataChanged(itemComp, this.itemIndex2DataIndex(i));
                }
            }
            this._contentPositionChanged = false;
        }
    }

    public resetLayout() {
        let scrollView = this.recycleList.scrollView;
        let tempNode = this.recycleList.tempNode;
        let view = this.recycleList.view;
        let content = this.recycleList.content;

        this._rowFirstIndex = this._rowLastIndex = this._colFirstIndex = this._colLastIndex = -1;

        if (scrollView.vertical && scrollView.horizontal) {
            this._contentRow = this.getLessLengthMaxNum(tempNode.height, this.spaceY, content.height);
            this._viewRow = this.getLessLengthMaxNum(tempNode.height, this.spaceY, view.height);
            this.row = Math.min(this._contentRow, this.getGreaterLengthMinNum(tempNode.height, this.spaceY, view.height));

            this._contentCol = this.getLessLengthMaxNum(tempNode.width, this.spaceX, content.width);
            this._viewCol = this.getLessLengthMaxNum(tempNode.width, this.spaceX, view.width);
            this.col = Math.min(this._contentCol, this.getGreaterLengthMinNum(tempNode.height, this.spaceY, view.width));
        } else if (scrollView.vertical) {
            this.col = this._contentCol = this._viewCol = this.getLessLengthMaxNum(tempNode.width, this.spaceX, view.width);
            this._contentRow = this.col !== 0 ? Math.ceil(this.recycleList.getDataLength() / this.col) : 0;

            this._viewRow = this.getLessLengthMaxNum(tempNode.height, this.spaceY, view.height);
            this.row = Math.min(this._contentRow, this.getGreaterLengthMinNum(tempNode.height, this.spaceY, view.height));
        } else {
            this.row = this._contentRow = this._viewRow = this.getLessLengthMaxNum(tempNode.height, this.spaceY, view.height);
            this._contentCol = this.row !== 0 ? Math.ceil(this.recycleList.getDataLength() / this.row) : 0;

            this._viewCol = this.getLessLengthMaxNum(tempNode.width, this.spaceX, view.width);
            this.col = Math.min(this._contentCol, this.getGreaterLengthMinNum(tempNode.height, this.spaceY, view.width));
        }

        scrollView.content.height = this._contentRow === 0 ? 0 : tempNode.height + (this._contentRow - 1) * (tempNode.height + this.spaceY);
        scrollView.content.width = this._contentCol === 0 ? 0 : tempNode.width + (this._contentCol - 1) * (tempNode.width + this.spaceX);

        this.setContentPosition(cc.Vec2.ZERO);
    }

    public onContentPositionChanged() {
        if (this.row === 0 || this.col === 0) return;
        let tempNode = this.recycleList.tempNode;
        let contentOffset = this.getContentOffset();
        contentOffset.x = contentOffset.x < 0 ? 0 : contentOffset.x;
        contentOffset.y = contentOffset.y < 0 ? 0 : contentOffset.y;

        this._rowFirstIndex = Math.floor(contentOffset.y / (tempNode.height + this.spaceY));
        this._rowLastIndex = this._rowFirstIndex + this.row - 1;

        this._colFirstIndex = Math.floor(contentOffset.x / (tempNode.width + this.spaceX));
        this._colLastIndex = this._colFirstIndex + this.col - 1;
        this._contentPositionChanged = true;
    }

    /**
     * 获取滚动偏移
     * @param dataIndex 
     * @param percentX 
     * @param percentY 
     * @returns 
     */
    public getScrollOffset(dataIndex: number, percentX: number, percentY: number): cc.Vec2 {
        let view = this.recycleList.view;
        let startPos = this.getItemPositionByDataIndex(dataIndex);
        let viewLocalPos = cc.Vec2.ZERO;
        viewLocalPos.x = percentX * view.width - view.anchorX * view.width;
        viewLocalPos.y = percentY * view.height - view.anchorY * view.height;
        let viewWorldPos = view.convertToWorldSpaceAR(viewLocalPos);
        let endPos = this.recycleList.content.convertToNodeSpaceAR(viewWorldPos);
        let offset = this.recycleList.scrollView.getScrollOffset().add(endPos.sub(startPos));
        offset.x *= -1;
        return offset;
    }

    /**
     * 给定指定长度,获取小于指定长度所需步长和间距的最大数量
     * @param step 
     * @param space 
     * @param length 
     * @returns 
     */
    private getLessLengthMaxNum(step: number, space: number, length: number) {
        return step > length ? 0 : Math.floor((length - step) / (step + space)) + 1;
    }

    /**
     * 给定指定长度，获取大于指定长度所需步长和间距的最小数量
     * @param step 
     * @param space 
     * @param length 
     * @returns 
     */
    private getGreaterLengthMinNum(step: number, space: number, length: number) {
        return step > length ? 2 : Math.ceil((length - step) / (step + space)) + 2;
    }

    /**
     * 通过数据下标获取item坐标
     * @param dataIndex 
     * @returns 
     */
    public getItemPositionByDataIndex(dataIndex: number): cc.Vec2 {
        let itemNode = this.recycleList.tempNode;
        let content = this.recycleList.content;
        let rowColIndex = this.dataIndex2RowColIndex(dataIndex);
        //item左下角相对于content左下角坐标
        let pos = cc.Vec2.ZERO;
        pos.x = this.horizontalLayout === HorizontalLayout.LEFT_TO_RIGHT ?
            rowColIndex.y * (this.recycleList.tempNode.width + this.spaceX) :
            (this._contentCol - rowColIndex.y - 1) * (this.recycleList.tempNode.width + this.spaceX);
        pos.y = this.verticalLayout === VerticalLayout.BOTTOM_TO_TOP ?
            rowColIndex.x * (this.recycleList.tempNode.height + this.spaceY) :
            (this._contentRow - rowColIndex.x - 1) * (this.recycleList.tempNode.height + this.spaceY);

        //将item坐标转换为相对content锚点为原点的坐标
        pos.addSelf(itemNode.getAnchorPoint().multiply(cc.v2(itemNode.width, itemNode.height)));
        pos.subSelf(content.getAnchorPoint().multiply(cc.v2(content.width, content.height)));
        return pos;
    }

    /**
     * 将index限制在[minIndex,maxIndex]范围
     * @param index 
     * @param step 
     * @param minIndex 
     * @param maxIndex 
     * @returns 
     */
    private clampIndex(index: number, step: number, minIndex: number, maxIndex: number): number {
        if (index < minIndex) {
            while (index + step <= maxIndex) {
                index += step;
            }
        } else if (index > maxIndex) {
            while (index - step >= minIndex) {
                index -= step;
            }
        }
        return index;
    }

    /**
     * data下标转换为data行列下标
     * @param dataIndex 
     * @returns 
     */
    private dataIndex2RowColIndex(dataIndex: number): cc.Vec2 {
        return dataIndex < 0 ? cc.v2(-1, -1) : cc.v2(Math.floor(dataIndex / this._contentCol), dataIndex % this._contentCol);
    }

    /**
     * item下标转换为item行列下标
     * @param itemIndex 
     * @returns 
     */
    private itemIndex2RowColIndex(itemIndex: number): cc.Vec2 {
        return itemIndex < 0 ? cc.v2(-1, -1) : cc.v2(Math.floor(itemIndex / this.col), itemIndex % this.col);
    }

    /**
     * data下标转换为item下标
     * @param dataIndex 
     * @returns 
     */
    public dataIndex2ItemIndex(dataIndex: number): number {
        let dataRowColIndex = this.dataIndex2RowColIndex(dataIndex);
        let itemRowIndex = this.clampIndex(dataRowColIndex.x, this.row, 0, this.row - 1);
        let itemColIndex = this.clampIndex(dataRowColIndex.y, this.col, 0, this.col - 1);
        return itemRowIndex * this.col + itemColIndex;
    }

    /**
     * item下标转换为data下标
     * @param itemIndex 
     * @returns 
     */
    public itemIndex2DataIndex(itemIndex: number): number {
        let itemRowColIndex = this.itemIndex2RowColIndex(itemIndex);
        let dataRowIndex = this.clampIndex(itemRowColIndex.x, this.row, this._rowFirstIndex, this._rowLastIndex);
        let dataColIndex = this.clampIndex(itemRowColIndex.y, this.col, this._colFirstIndex, this._colLastIndex);
        return dataRowIndex * this._contentCol + dataColIndex;
    }
}
